/*
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */
package org.evosuite.graphs.cfg;

import org.evosuite.graphs.GraphPool;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Frame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * This classed is used to create the RawControlFlowGraph which can then be used
 * to create the ActualControlFlowGraph
 * <p>
 * When analyzing a CUT the BytecodeAnalyzer creates an instance of this class
 * for each method contained in it
 * <p>
 * This class's methods get called in the following order:
 * <p>
 * - upon constructing, the method at hand is registered via
 * registerMethodNode() which fills the BytecodeInstructionPool with all
 * instructions inside that method
 * <p>
 * - then registerControlFlowEdge() is called by the BytecodeAnalyzer for each
 * possible transition from one byteCode instruction to another within the
 * current method. In this step the CFGGenerator asks the
 * BytecodeInstructionPool for the previously created instructions and fills up
 * it's RawControlFlowGraph
 * <p>
 * After those calls the RawControlFlowGraph of the method at hand is complete
 * It should contain a Vertex for each BytecodeInstruction inside the specified
 * method and an edge for every possible transition between these instructions
 *
 * @author Andre Mis
 */
public class CFGGenerator {

    private static final Logger logger = LoggerFactory.getLogger(CFGGenerator.class);

    private RawControlFlowGraph rawGraph;

    private boolean nodeRegistered = false;
    private MethodNode currentMethod;
    private String className;
    private String methodName;
    private final ClassLoader classLoader;

    /**
     * Initializes this generator to generate the CFG for the method identified
     * by the given parameters
     * <p>
     * Calls registerMethodNode() which in turn calls
     * BytecodeInstructionPool.registerMethodNode() leading to the creation of
     * all BytecodeInstruction instances for the method at hand
     * <p>
     * TODO might not want to give asm.MethodNode to the outside, but rather a
     * MyMethodNode extended from BytecodeInstruction or something
     *
     * @param className  a {@link java.lang.String} object.
     * @param methodName a {@link java.lang.String} object.
     * @param node       a {@link org.objectweb.asm.tree.MethodNode} object.
     */
    public CFGGenerator(ClassLoader classLoader, String className, String methodName,
                        MethodNode node) {
        this.classLoader = classLoader;
        registerMethodNode(node, className, methodName);
    }

    /**
     * Adds the RawControlFlowGraph created by this instance to the GraphPool,
     * computes the resulting ActualControlFlowGraph
     */
    public void registerCFGs() {

        int removed = getRawGraph().removeIsolatedNodes();
        if (removed > 0)
            logger.info("removed isolated nodes: " + removed + " in " + methodName);

        // non-minimized cfg needed for defuse-coverage and control
        // dependence calculation
        GraphPool.getInstance(classLoader).registerRawCFG(getRawGraph());
        GraphPool.getInstance(classLoader).registerActualCFG(computeActualCFG());
    }

    // build up the graph

    private void registerMethodNode(MethodNode currentMethod, String className,
                                    String methodName) {
        if (nodeRegistered)
            throw new IllegalStateException(
                    "registerMethodNode must not be called more than once for each instance of CFGGenerator");
        if (currentMethod == null || methodName == null || className == null)
            throw new IllegalArgumentException("null given");

        this.currentMethod = currentMethod;
        this.className = className;
        this.methodName = methodName;

        this.rawGraph = new RawControlFlowGraph(classLoader, className, methodName,
                currentMethod.access);

        List<BytecodeInstruction> instructionsInMethod = BytecodeInstructionPool.getInstance(classLoader).registerMethodNode(currentMethod,
                className,
                methodName);

        // sometimes there is a Label at the very end of a method without a
        // controlFlowEdge to it. In order to keep the graph as connected as
        // possible and since this is just a label we will simply ignore these
        int count = 0;
        for (BytecodeInstruction ins : instructionsInMethod) {
            count++;
            if (!ins.isLabel() || count < instructionsInMethod.size())
                rawGraph.addVertex(ins);
        }

        nodeRegistered = true;
    }

    /**
     * Internal management of fields and actual building up of the rawGraph
     * <p>
     * Is called by the corresponding BytecodeAnalyzer whenever it detects a
     * control flow edge
     *
     * @param src             a int.
     * @param dst             a int.
     * @param frames          an array of {@link org.objectweb.asm.tree.analysis.Frame}
     *                        objects.
     * @param isExceptionEdge a boolean.
     */
    public void registerControlFlowEdge(int src, int dst, Frame[] frames,
                                        boolean isExceptionEdge) {
        if (!nodeRegistered)
            throw new IllegalStateException(
                    "CFGGenrator.registerControlFlowEdge() cannot be called unless registerMethodNode() was called first");
        if (frames == null)
            throw new IllegalArgumentException("null given");
        CFGFrame srcFrame = (CFGFrame) frames[src];
        Frame dstFrame = frames[dst];

        if (srcFrame == null)
            throw new IllegalArgumentException(
                    "expect given frames to know srcFrame for " + src);

        if (dstFrame == null) {
            // documentation of getFrames() tells us the following:
            // Returns:
            // the symbolic state of the execution stack frame at each bytecode
            // instruction of the method. The size of the returned array is
            // equal to the number of instructions (and labels) of the method. A
            // given frame is null if the corresponding instruction cannot be
            // reached, or if an error occured during the analysis of the
            // method.
            // so let's say we expect the analyzer to return null only if
            // dst is not reachable and if that happens we just suppress the
            // corresponding ControlFlowEdge for now
            // TODO can the CFG become disconnected like that?
            return;
        }

        srcFrame.successors.put(dst, (CFGFrame) dstFrame);

        AbstractInsnNode srcNode = currentMethod.instructions.get(src);
        AbstractInsnNode dstNode = currentMethod.instructions.get(dst);

        // those nodes should have gotten registered by registerMethodNode()
        BytecodeInstruction srcInstruction = BytecodeInstructionPool.getInstance(classLoader).getInstruction(className,
                methodName,
                src,
                srcNode);
        BytecodeInstruction dstInstruction = BytecodeInstructionPool.getInstance(classLoader).getInstruction(className,
                methodName,
                dst,
                dstNode);

        srcInstruction.setCFGFrame(srcFrame);

        if (dstInstruction == null)
            throw new IllegalStateException(
                    "expect BytecodeInstructionPool to know the instructions in the method of this edge");

        if (null == rawGraph.addEdge(srcInstruction, dstInstruction, isExceptionEdge))
            logger.error("internal error while adding edge");
    }

    /**
     * Computes the ActualCFG with BasicBlocks rather then BytecodeInstructions
     * for this RawCFG.
     * <p>
     * See ActualControlFlowGraph and GraphPool for further details.
     *
     * @return a {@link org.evosuite.graphs.cfg.ActualControlFlowGraph} object.
     */
    public ActualControlFlowGraph computeActualCFG() {
        BytecodeInstructionPool.getInstance(classLoader).logInstructionsIn(className,
                methodName);

        return new ActualControlFlowGraph(rawGraph);
    }

    // getter

    /**
     * <p>
     * Getter for the field <code>rawGraph</code>.
     * </p>
     *
     * @return a {@link org.evosuite.graphs.cfg.RawControlFlowGraph} object.
     */
    protected RawControlFlowGraph getRawGraph() {
        return rawGraph;
    }

    /**
     * <p>
     * Getter for the field <code>className</code>.
     * </p>
     *
     * @return a {@link java.lang.String} object.
     */
    public String getClassName() {
        return className;
    }

    /**
     * <p>
     * Getter for the field <code>methodName</code>.
     * </p>
     *
     * @return a {@link java.lang.String} object.
     */
    public String getMethodName() {
        return methodName;
    }

}
